
#include "net_sdl.h"
#include "string.h"

#ifdef NET_SDL

using namespace WtfEngine;


/**** Generic Socket ****/

Socket::Socket(const IPaddress& ip):
	mIpAddress(ip.host), mPort(ip.port), mStatus(UNINITIALIZED) {
};
Socket::Socket(tIpAddress ip, tPort port):
	mIpAddress(ip), mPort(port), mStatus(UNINITIALIZED) {
};
Socket::~Socket() {
};

void Socket::Connect() {
	if(mStatus == CONNECTED)
		throw new std::logic_error("Socket already connected");
	else if(mStatus == LISTENING)
		throw new std::logic_error("Cannot connect server socket");
	
	mStatus = CONNECTING;
	NetworkManager::GetInstance().AddSocket(this);
};

void Socket::Disconnect() {
	if(mStatus == CONNECTED || mStatus == LISTENING || mStatus == CONNECTING) {
		mStatus = CLOSING;
		NetworkManager::GetInstance().RemoveSocket(this);
	} else {
		throw new std::logic_error("Socket is not open - cannot close");
	};
};


/**** TCP ****/

TcpSocket::TcpSocket(const TCPsocket& socket):
	Socket(*SDLNet_TCP_GetPeerAddress(socket)) {
	// This is a pointer. Set it to the specified socket.
	this->mSocket = socket;
};

TcpSocket::~TcpSocket() {
	if(mSocket) Disconnect();
};

void TcpSocket::Connect() {
	IPaddress ip = {mIpAddress, mPort};

	Socket::Connect();

	mSocket = SDLNet_TCP_Open(&ip);
	if(mIpAddress == INADDR_ANY || mIpAddress == INADDR_NONE)
		mStatus = LISTENING;
};

void TcpSocket::Disconnect() {
	// TODO
};

void TcpSocket::AddToSet(SDLNet_SocketSet& set) {
	
};

void TcpSocket::RemoveFromSet(SDLNet_SocketSet& set) {
	
};

bool TcpSocket::CheckData() {
	bool		HasData = false;
	TCPsocket	client;
	
	if(SDLNet_SocketReady(mSocket)) {
		if((client = SDLNet_TCP_Accept(mSocket))) {
			
			//
			// A client has connected
			//
			
			Kernel::GetInstance().GetGameState()->ProcessEvent(
				new SocketClientConnectEvent(this, new TcpSocket(client)));

			HasData = true;
		} else {
			
			//
			// We have data
			//
			
			Kernel::GetInstance().GetGameState()->ProcessEvent(
				new SocketDataEvent(this));

			HasData = true;
		};
	};

	return HasData;
};

void TcpSocket::SendPacket(const IPacket::tRef& rPacket) {
	
};

IPacket::tRef TcpSocket::RecvPacket() {
	// TODO
	return IPacket::tRef();
};


/**** Network Manager ****/

NetworkManager::NetworkManager():
		Task<NetworkManager>(0, "Network"), Loggable<NetworkManager>("Network") {
	
	//
	// Create the socket set
	//
	
	mSocketSet = SDLNet_AllocSocketSet(MaxSockets);
	
	if(!mSocketSet) {
		throw new std::runtime_error(String("Failed to create socket set: ") + SDLNet_GetError());
	};
};

void NetworkManager::AddSocket(const ISocket::tRef& rSocket) {
	Socket * pSocket = dynamic_cast<Socket *>(&*rSocket);
	if(pSocket == NULL) {
		throw new std::logic_error("Unsupported socket");
	};
	
	pSocket->AddToSet(mSocketSet);
	mSockets.push_back(pSocket);
};
			
void NetworkManager::RemoveSocket(const ISocket::tRef& rSocket) {
	Socket * pSocket = dynamic_cast<Socket *>(&*rSocket);
	if(pSocket == NULL) {
		throw new std::logic_error("Unsupported socket");
	};
	
	pSocket->RemoveFromSet(mSocketSet);
	mSockets.remove(pSocket);
};

void NetworkManager::Run() {
	std::list<Socket::tRef>::iterator	i;
	int numReady = SDLNet_CheckSockets(mSocketSet, 0);
	
	//
	// If there are sockets ready, check them until we find all
	// the ready sockets, and fire data events for them.
	//
		
	for(i = mSockets.begin(); (numReady > 0) && (i != mSockets.end()); i++) {
		if((*i)->CheckData())
			numReady--;
	};
	
	
	//
	// Check for errors
	//
	
	if(numReady > 0) {
		Log(LOG_WARNING, String("Ready sockets missed"));
	} else if(numReady < 0) {
		Log(LOG_ERROR, String("Failed to check sockets: ") + SDLNet_GetError());
	};
};

#endif // NET_SDL
